#ifndef RANDOM_NUMBER_GENERATOR_H
#define RANDOM_NUMBER_GENERATOR_H

#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <stdexcept>
#include <stdint.h>

class RandomNumberGenerator 
{
	public:
		RandomNumberGenerator() 
		{
			if(!hasinit_)
			init();
		}
		~RandomNumberGenerator()
		{
		}

		template <class K> K generate()
		{
			int size = sizeof( K );
			uint8_t buff[size];
			uint8_t* where = buff;
			int bytesLeft = size;
			while( bytesLeft )
			{
				int readSize = 0;
				if( (readSize = read( spc_devurand_fd_, where, bytesLeft )) == -1 )
				{
					throw std::runtime_error( "RandomNumberGenerator::generate can't read urandom" );
				}

				where += readSize;
				bytesLeft -= readSize;
			}

			where = buff;

			return *( (K*) ( where ) );
		}


	private:

		void makeFDNoBlock()
		{
			int flags;

			flags = fcntl( spc_devrand_fd_noblock_, F_GETFL );
			if( flags < 0 )
			{
				throw std::runtime_error( "RandomNumberGenerator::makeFDNoBlock() failed on F_GETFL");
			}

			flags |= O_NONBLOCK;

			if( fcntl( spc_devrand_fd_noblock_, F_SETFL, flags ) == -1 ) 
			{
				throw std::runtime_error( "RandomNumberGenerator::makeFDNoBlock() failed on F_SETFL");
			}

		}


		void init()
		{
			spc_devrand_fd_ = open( "/dev/random", O_RDONLY );
			spc_devrand_fd_noblock_ = open( "/dev/random", O_RDONLY );
			spc_devurand_fd_ = open( "/dev/urandom", O_RDONLY );

			if( spc_devrand_fd_ == -1 || spc_devrand_fd_noblock_ == -1 )
			{
				throw std::runtime_error( "RandomNumberGenerator::init() failed to open /dev/random" );
			}
			
			if( spc_devurand_fd_ == -1 )
			{
				throw std::runtime_error( "RandomNumberGenerator::init() failed to open /dev/urandom" );
			}

			makeFDNoBlock();

			hasinit_ = true;
		}

		static bool hasinit_;
		static int spc_devrand_fd_;
		static int spc_devrand_fd_noblock_;
		static int spc_devurand_fd_;
};

bool RandomNumberGenerator::hasinit_ = false;
int RandomNumberGenerator::spc_devrand_fd_ = -1;
int RandomNumberGenerator::spc_devrand_fd_noblock_ = -1;
int RandomNumberGenerator::spc_devurand_fd_ = -1;

#endif
